package com.cq.hd.order.processor.settle;

import cn.hutool.core.date.DateUtil;
import com.cq.hd.common.enums.biz.MonthSettleBillStatusEnum;
import com.cq.hd.common.response.Throw;
import com.cq.hd.member.api.TbAppUserApi;
import com.cq.hd.member.api.TbMerchantApi;
import com.cq.hd.member.api.dto.MerchantAmtAddDto;
import com.cq.hd.member.api.dto.MerchantAmtBillSaveDto;
import com.cq.hd.member.api.dto.OrderSettleUpdateDto;
import com.cq.hd.member.api.vo.MerchantVo;
import com.cq.hd.order.api.dto.MonthSettleGroupKeyDto;
import com.cq.hd.order.api.vo.OrderSettleBillVo;
import com.cq.hd.order.mapper.TbMonthSettleBillMapper;
import com.cq.hd.order.po.TbMonthSettleBillPo;
import com.cq.hd.order.po.TbOrderSettleBillItemPo;
import com.cq.hd.order.service.TbOrderSettleBillItemService;
import com.cq.hd.order.service.TbOrderSettleBillService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 月度结算处理器基类
 *
 * @author much
 * @since 2024/04/14 20:29
 */
@Slf4j
public abstract class AbstractMonthSettleProcessor {

    @Resource
    protected TbOrderSettleBillService orderSettleBillService;
    @Resource
    protected TbMonthSettleBillMapper monthSettleBillMapper;
    @Resource
    protected TbOrderSettleBillItemService orderSettleBillItemService;
    @Resource
    protected TbMerchantApi merchantApi;
    @Resource
    protected TbAppUserApi appUserApi;
    @Resource
    protected PlatformTransactionManager transactionManager;

    public void run() {
        List<OrderSettleBillVo> waitProcessBills = queryWaitProcessBills();
        log.info("[Month Settlement] - waitProcessBills size = {}", waitProcessBills.size());
        // 按商家 & 商户 & 账单日期为维度生成月度结算单
        Map<String, List<OrderSettleBillVo>> orderSettleBillMap = waitProcessBills.stream().collect(Collectors.groupingBy(this::groupByKey));
        orderSettleBillMap.forEach(this::processGroupDataInLoop);
        log.info("[Month Settlement] - waitProcessBills end");
    }

    /**
     * 处理分组数据
     *
     * @param groupKey 分组的key = businessId & merchantId & yyyy-MM
     * @param dataList 分组内的订单账单数据集
     */
    protected void processGroupDataInLoop(String groupKey, List<OrderSettleBillVo> dataList) {
        log.info("[Month Settlement] - processGroupDataInLoop - {} - start", groupKey);
        MonthSettleGroupKeyDto groupKeyDto = parseGroupKey(groupKey);
        MerchantVo merchantVo = merchantApi.getById(groupKeyDto.getMerchantId()).unpack();

        TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionAttribute());
        try {
            // 1. 创建月度结算单
            TbMonthSettleBillPo monthSettleBill = getMonthSettleBill(groupKeyDto, dataList);
            // 2. 更新orderSettleBills
            updateOrderSettleBills(monthSettleBill.getId(), dataList);
            // 3. 更新商户可提补贴余额和资金明细
            updateMerchantAmt(merchantVo, dataList);

            transactionManager.commit(transaction);
        } catch (Exception e) {
            //回滚事务
            transactionManager.rollback(transaction);
            log.error("[Month Settlement] - rollback", e);
            Throw.isBusinessException(e.getMessage());
        }
        log.info("[Month Settlement] - {} - end", groupKey);
    }

    private String groupByKey(OrderSettleBillVo orderSettleBillVo) {
        return orderSettleBillVo.getBusinessId() +
                "&" + orderSettleBillVo.getMerchantId() +
                "&" + DateUtil.format(orderSettleBillVo.getTradeTime(), "yyyy-MM");
    }

    protected MonthSettleGroupKeyDto parseGroupKey(String groupKey) {
        String[] split = groupKey.split("&");
        return MonthSettleGroupKeyDto.builder()
                .businessId(Long.valueOf(split[0]))
                .merchantId(Long.valueOf(split[1]))
                .settleDate(split[2])
                .build();
    }

    private void updateOrderSettleBills(Long monthSettleBillId, List<OrderSettleBillVo> orderSettleBills) {
        List<Long> orderSettleBillIds = orderSettleBills.stream().map(OrderSettleBillVo::getId).collect(Collectors.toList());
        int result = orderSettleBillService.monthSettleOrderSettleBills(monthSettleBillId, orderSettleBillIds);
        if (result != orderSettleBillIds.size()) {
            log.error("[Month Settlement] - update orderSettleBills error: billSize={}, result={}", orderSettleBillIds.size(), result);
            Throw.isBusinessException("[Month Settlement] - update orderSettleBills error");
        }
    }

    /**
     * 更新商户可提补贴余额和资金明细
     *
     * @param merchantVo 商户
     * @param orderSettleBills 订单账单
     */
    private void updateMerchantAmt(MerchantVo merchantVo, List<OrderSettleBillVo> orderSettleBills) {
        List<Long> billIds = orderSettleBills.stream().map(OrderSettleBillVo::getId).collect(Collectors.toList());
        List<TbOrderSettleBillItemPo> orderSettleBillItems = orderSettleBillItemService.listByBillIds(billIds);
        if (CollectionUtils.isEmpty(orderSettleBillItems)) {
            log.error("[Month Settlement] - orderSettleBillItem not found");
            Throw.isBusinessException("[Month Settlement] - orderSettleBillItem not found");
        }
        // 余额变更
        MerchantAmtAddDto merchantAmtAddDto = calculateMerchantAmtAddDto(merchantVo.getId(), orderSettleBillItems);
        // 资金流水
        List<MerchantAmtBillSaveDto> merchantAmtBillSaveDtoList = generateMerchantAmtBills(merchantVo, orderSettleBillItems);

        OrderSettleUpdateDto orderSettleUpdateDto = new OrderSettleUpdateDto();
        orderSettleUpdateDto.setOrderId(0L);
        orderSettleUpdateDto.setMerchantAmtAdds(Collections.singletonList(merchantAmtAddDto));
        orderSettleUpdateDto.setMerchantAmtBillSaves(merchantAmtBillSaveDtoList);
        Boolean updateOrderSettleAmtState = appUserApi.updateOrderSettleAmt(orderSettleUpdateDto).unpack();
        if (updateOrderSettleAmtState == null || !updateOrderSettleAmtState) {
            log.error("[Month Settlement] - 更新商户余额失败");
            Throw.isBusinessException("更新商户余额失败");
        }

    }

    protected TbMonthSettleBillPo newMonthSettleBill(MonthSettleGroupKeyDto groupKeyDto, List<OrderSettleBillVo> orderSettleBills, Integer type) {
        TbMonthSettleBillPo tbMonthSettleBillPo = new TbMonthSettleBillPo();
        tbMonthSettleBillPo.setBusinessId(groupKeyDto.getBusinessId());
        tbMonthSettleBillPo.setMerchantId(groupKeyDto.getMerchantId());
        tbMonthSettleBillPo.setSettleDate(groupKeyDto.getSettleDate());
        tbMonthSettleBillPo.setStatus(MonthSettleBillStatusEnum.NEW.getValue());
        tbMonthSettleBillPo.setType(type);
        tbMonthSettleBillPo.setCreateTime(LocalDateTime.now());
        tbMonthSettleBillPo.setUpdateTime(LocalDateTime.now());
        // 累计金额
        BigDecimal tradeAmt = BigDecimal.ZERO;
        BigDecimal receiveAmt = BigDecimal.ZERO;
        BigDecimal feeAmt = BigDecimal.ZERO;
        for (OrderSettleBillVo orderSettleBillVo : orderSettleBills) {
            tradeAmt = tradeAmt.add(Optional.ofNullable(orderSettleBillVo.getTradeAmt()).orElse(BigDecimal.ZERO));
            receiveAmt = receiveAmt.add(Optional.ofNullable(orderSettleBillVo.getReceiveAmt()).orElse(BigDecimal.ZERO));
            feeAmt = feeAmt.add(Optional.ofNullable(orderSettleBillVo.getFeeAmt()).orElse(BigDecimal.ZERO));
        }
        tbMonthSettleBillPo.setTradeAmt(tradeAmt);
        tbMonthSettleBillPo.setReceiveAmt(receiveAmt);
        tbMonthSettleBillPo.setFeeAmt(feeAmt);
        return tbMonthSettleBillPo;
    }

    /**
     * 查询待处理的OrderSettleBill
     */
    protected abstract List<OrderSettleBillVo> queryWaitProcessBills();

    /**
     * 获取月度结算单（新建或者使用历史月度结算单）
     */
    protected abstract TbMonthSettleBillPo getMonthSettleBill(MonthSettleGroupKeyDto groupKeyDto, List<OrderSettleBillVo> dataList);

    /**
     * 计算商户需要增加的金额
     */
    protected abstract MerchantAmtAddDto calculateMerchantAmtAddDto(Long merchantId, List<TbOrderSettleBillItemPo> orderSettleBillItems);

    /**
     * 生成资金明细
     */
    protected abstract List<MerchantAmtBillSaveDto> generateMerchantAmtBills(MerchantVo merchantVo, List<TbOrderSettleBillItemPo> orderSettleBillItems);

}
